package jetbrains.buildServer.cuta.logic;

import jetbrains.buildServer.configuration.ChangeListener;
import jetbrains.buildServer.configuration.FileWatcher;
import jetbrains.buildServer.cuta.logic.operators.ExternalContext;
import jetbrains.buildServer.cuta.logic.operators.SequenceOperator;
import jetbrains.buildServer.serverSide.ServerPaths;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.util.*;

import static jetbrains.buildServer.cuta.utils.DomUtil.*;



/**
 * CuTa configuration.
 *
 * @author Leonid Bushuev from JetBrains
 */
public class CutaDef
{

    @NotNull
    private final ServerPaths myServerPaths;

    @NotNull
    private final ExternalContext myExtContext;


    @NotNull
    private final File myConfigFile;

    @NotNull
    private final FileWatcher myConfigFileWatcher;

    @NotNull
    public final List<ChangeListener> refreshListeners =
            new LinkedList<ChangeListener>();



    /**
     * The root node of the configuration file.
     */
    public Element configRoot;

    /**
     * All CuTa pages.
     * A map of page identifier (see {@link PageDef#id}) to the page.
     */
    public final Map<String,PageDef> pages =
            new TreeMap<String, PageDef>();


    public final Map<String,CutaMainTab> tabs =
            new LinkedHashMap<String,CutaMainTab>();


    private long myFileTimestamp = 0;
    private Object myFileRefreshingLock = new Object();


    private int idseq = 0;





    public CutaDef(@NotNull ServerPaths serverPaths, ExternalContext extContext)
    {
        myServerPaths = serverPaths;
        myExtContext = extContext;

        myConfigFile = new File(myServerPaths.getConfigDir(), "cuta-config.xml");
        myConfigFileWatcher = new FileWatcher(myConfigFile);

        myConfigFileWatcher.registerListener(new ChangeListener()
        {
            public void changeOccured(String requestor) { refreshConfig(); }
        });
    }


    public void init()
    {
        refreshConfig();
        myConfigFileWatcher.start();
    }


    public void refreshConfig()
    {
        boolean refreshed = false;

        synchronized (myFileRefreshingLock)
        {
            if (myConfigFile.exists() && myConfigFile.lastModified() > myFileTimestamp)
            {
                readConfigFile(myConfigFile);
                refreshed = true;
            }
        }

        if (refreshed)
            for (ChangeListener listener: refreshListeners)
                listener.changeOccured(null);

        myConfigFileWatcher.resetChanged();
    }


    private void readConfigFile(final @NotNull File file)
    {
        if (file.exists())
        {
            myFileTimestamp = file.lastModified();
            configRoot = readXmlFile(file);
            parseConfigFile();
        }
        else
        {
            configRoot = null;
            pages.clear();
            tabs.clear();
            myFileTimestamp = 0;
        }
    }


    private void parseConfigFile()
    {
        List pageElements = configRoot.getChildren("page");
        for (Object pageElement: pageElements)
            parsePageConfig((Element) pageElement);
        prepareTabs();
    }


    private void parsePageConfig(Element pageElement)
    {
        String id = getAttrString(pageElement, "id", null);
        if (id == null)
            throw new RuntimeException("CuTa: Page has no 'id' attribute.");

        String kindStr = getAttrString(pageElement, "kind", null);
        if (kindStr == null)
            throw new RuntimeException("CuTa: Page has no 'kind' attribute.");
        CutaSheetKind kind = CutaSheetKind.codes.get(kindStr);
        if (kind == null)
            throw new RuntimeException("CuTa: Invalid kind '"+kindStr+"'.");

        String tab = getAttrString(pageElement, "tab", id);

        PageDef page = new PageDef(id, kind, tab);
        pages.put(id, page);

        page.title = getNestedContent(pageElement, "title", null);
        page.header = getNestedContent(pageElement, "header", null);
        page.footer = getNestedContent(pageElement, "footer", null);

        Element columnsElement = pageElement.getChild("columns");
        if (columnsElement != null)
        {
            List columnElementList =
                    columnsElement.getChildren("column");
            for (Object columnElement: columnElementList)
                parseColumnConfig((Element) columnElement, page);
        }
    }


    private void parseColumnConfig(Element columnElement, PageDef page)
    {
        String id = getAttrString(columnElement, "id", null);
        if (id == null)
            id = "column" + (++idseq);
        ColumnDef column = new ColumnDef(id);
        page.columns.add(column);

        column.caption = getNestedContent(columnElement, "caption", "");

        Element cellElement = columnElement.getChild("cell");
        column.content = new SequenceOperator();
        column.content.init(cellElement, column, myExtContext);
    }


    private static Element readXmlFile(File file)
    {
        try
        {
            SAXBuilder sax = new SAXBuilder();
            org.jdom.Document xdoc = sax.build(file);
            return xdoc.getRootElement();
        }
        catch (Exception e)
        {
            throw new RuntimeException("CuTa: Failed to read configuration file "+file.getAbsolutePath(), e);
        }
    }


    private void prepareTabs()
    {
        for (PageDef page: pages.values())
        {
            CutaMainTab mainTab = tabs.get(page.tab1);
            if (mainTab == null)
            {
                mainTab = new CutaMainTab(page.tab1);
                tabs.put(mainTab.name, mainTab);
            }

            String subTabName = page.tab2 == null ? page.tab1 : page.tab2;
            CutaSubTab subTab = new CutaSubTab(subTabName, page);
            mainTab.addSubTab(subTab);
        }

        for (CutaMainTab mainTab: tabs.values())
        {
            if (mainTab.getSubTabCount() == 1)
                mainTab.setPage(mainTab.getSubTabs().iterator().next().getPage());
        }
    }




}
